/**
 * @author Da Mu
 * @version 1.1.0
 * @mail 317782199@qq.com
 */
import { Scene3D, ExtendedObject3D, ExtendedMesh, THREE, FLAT } from "@enable3d/phaser-extension";
/**
 * @url -https://github.com/isaac-mason/recast-navigation-js
 * @url -https://github.com/isaac-mason/recast-navigation-js/blob/main/examples/three-vanilla-example/index.html
 * @url -https://recast-navigation-js.isaacmason.com/?path=/story/crowd-crowd-with-multiple-agents--crowd-with-multiple-agents
 * @abstract -Navigation Grid
 */
import {
    init as initRecastNavigation,
    NavMeshQuery,
} from '@recast-navigation/core';
import {
    NavMeshHelper,
    getPositionsAndIndices,
} from '@recast-navigation/three';
import gsap from "gsap";
import { generateSoloNavMesh } from '@recast-navigation/generators';
import proxy from '@/config/host';
import { socket } from '@/utils/Socket';
import { HtmlSubscription } from '@/utils/HtmlSubscription';
import LinkedList from "@/utils/LinkedList";


let ZOMB_COORD = null;

let GO_INTO_ACTION = false;

let ATTACK_LOCK_GHOST_UUID = '';
let ATTACK_LOCK_GHOST_POS = {};

/** Ghost decision-making */
let GPBD_KEY = '';
let GPBD_POLLER = null;

/** Player Constant speed */
const PROGRESS = 0;
const CONSTANT_VELOCITY = 1;

/** Pumpkin weapon Constant speed */
const PROGRESS_PUMPKIN = 0;
const CONSTANT_VELOCITY_PUMPKIN = 4;

/** Ghost Constant speed */
const PROGRESS_GHOST = 0;
let CONSTANT_VELOCITY_GHOST = 1;

/**
 * 
 * @param {*} curve 
 * @param {*} segments 
 * @returns distance
 */
function calculatePathLength(curve, segments = 1000) {
    let length = 0;
    let prevPoint = curve.getPoint(0);
    for (let i = 1; i <= segments; i++) {
        const t = i / segments;
        const point = curve.getPoint(t);
        length += prevPoint.distanceTo(point);
        prevPoint = point;
    }
    return length;
};

/**
 * 
 * @param {*} spline 
 * @param {*} targetDistance 
 * @param {*} segments 
 * @returns distance acquisition point
 */
function getPointAtDistance(spline, targetDistance, segments = 1000) {
    let accumulatedDistance = 0;
    let prevPoint = spline.getPoint(0);

    for (let i = 1; i <= segments; i++) {
        const t = i / segments;
        const point = spline.getPoint(t);
        const segmentLength = prevPoint.distanceTo(point);

        if (accumulatedDistance + segmentLength >= targetDistance) {
            const tSegment = (targetDistance - accumulatedDistance) / segmentLength;
            return prevPoint.clone().lerp(point, tSegment);
        }

        accumulatedDistance += segmentLength;
        prevPoint = point;
    }

    return spline.getPoint(1);
};

/**
 * 
 * @param {*} modules 
 * @param {*} interval 
 * @param {*} callback 
 * @returns tmer.stop
 */
function GhostPollingBehaviorDecision(modules, interval, callback) {
    if (!Array.isArray(modules) || modules.length === 0) {
        throw new Error('Invalid modules provided');
    };

    let index = 0;
    let isRunning = false;
    let timer = null;

    executeCallback();

    function executeCallback() {
        if (isRunning) return;

        isRunning = true;
        try {
            callback(modules[index], index);
            index = (index + 1) % modules.length;
        } finally {
            isRunning = false;
        };
    };

    timer = setInterval(executeCallback, interval);

    return {
        stop: function () {
            if (timer) {
                clearInterval(timer);
                timer = null;
            };
        }
    };
};

class SocketRoomData {
    constructor() {
        socket.on('connect', () => {
            // log.debug('WS Connected to server');
        });
        socket.on('disconnect', () => {
            // log.debug('WS Player disconnected');
        });

        /**@description Events  */
        socket.on('AdvertiseMessage', obj => {
            HtmlSubscription.publish("chat", obj);
        });
        /**@abstract Events */
        socket.on('AdvertiseCombatSysMessage', obj => {
            HtmlSubscription.publish("chat", obj);
        });
        /**@abstract Events */
        socket.on('AdvertisePickUpBrainMessage', obj => {
            HtmlSubscription.publish("chat", obj);
        });
        /**@abstract Events */
        socket.on('AdvertiseBulletRPMessage', obj => {
            HtmlSubscription.publish("chat", obj);
        });
        /**@abstract Events */
        socket.on('SceneGameZombieChat', obj => {
            HtmlSubscription.publish('chat', obj);
        });
    };

    getAdvertiseMessage() {
        socket.emit('AdvertiseMessage', 'NOT');
    };

    getAdvertiseSceneGameMessage(sw) {
        switch (sw) {
            case '1':
                socket.emit('AdvertiseCombatSysMessage', sw);
                break;
            case '2':
                socket.emit('AdvertiseCombatSysMessage', sw);
                break;
        };
    };

    getZombieCoordinates() {
        socket.emit('privateSceneGameMonsters', 'BM');
    };

    openZombieCoordinatesScheduledTasks() {
        socket.emit('privateSceneGameMonsters', 'LS');
    };

    getSceneGamePlayerInit(opt) {
        return new Promise((resolve, reject) => {
            if (opt === 'new') {
                socket.emit('SceneGamePlayerInit', 'NEW');
                /**@abstract Events */
                socket.on('SceneGamePlayerInit', obj => {
                    if (obj.playerDataTypeInit === 'OK') {
                        HtmlSubscription.publish("player_init");
                        HtmlSubscription.subscribe('player_initt_done', body => {
                            resolve(body.data);
                        });
                    };
                });
            };

            if (opt === 'read') {
                HtmlSubscription.publish("player_init");
                HtmlSubscription.subscribe('player_initt_done', body => {
                    resolve(body.data);
                });
            };
        });
    };

    on() {
        socket.connect();
    };

    off() {
        return new Promise((resolve, reject) => {
            socket.disconnect();
            resolve();
        });
    };
};

class Http {
    constructor() {
        this.http = `${proxy.development.API}`;
        this.path_img = `${proxy.development.PATH_IMG}`;
        this.path_models = `${proxy.development.PATH_MODELS}`;
    };
};

class HDR {
    init() {
        const renderer = this.third.renderer;
        renderer.antialias = true;
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
    };
};

class DebugModelsMaterial {
    static init() {
        return new THREE.MeshPhongMaterial({
            color: 'blue',
            opacity: .5,
            transparent: true
        });
    };
};

class CreateCurve {
    constructor(that, curve, debug) {
        this.spline = null;
        this.debug = debug;
        this.curve = curve;

        this.curvedraw(that);
    };

    curvedraw(that) {
        /** 
         * @description You need to find a modeler or use a binder to 
         * draw the vertex coordinates required for a traffic map, and use 
         * these vertex coordinates to create a path!
         */
        this.spline = new THREE.CatmullRomCurve3(
            this.curve,
            false
        );
        this.spline.curveType = 'catmullrom';
        this.spline.closed = false;
        this.spline.tension = 0.2;

        const points = this.spline.getPoints(this.curve.length * 20); /** subsection: The more curves there are, the smoother they become */
        const line = new THREE.Line(
            new THREE.BufferGeometry().setFromPoints(points),
            new THREE.LineBasicMaterial({ color: 0xff0000 })
        );
        line.position.y += 0.1;
        this.debug ? that.third.scene.add(line) : null;
    };
};

class CreateRayModel {
    constructor() {
        this.raycaster = new THREE.Raycaster();
        this.groundObjects = [];
        this.npcObjects = [];
        this.brainObjects = [];
    };

    updateGroundObjects(objects) {
        this.groundObjects = objects;
    };

    updateNpcObjects(objects) {
        this.npcObjects = objects;
    };

    updateBrainObjects(objects) {
        this.brainObjects = objects;
    };

    updateNpcObjectsItem(uuid) {
        const update = this.npcObjects.map(mesh => {
            if (mesh.uuid !== uuid) {
                return mesh;
            };
        }).filter(item => item !== undefined);
        this.npcObjects = update;
    };

    ground(that, ground, callback) {
        this.updateGroundObjects(ground);
        that.input.on('pointerdown', () => {
            const { x, y } = that.getPointer();
            this.raycaster.setFromCamera({ x, y }, that.third.camera);
            const intersection_ground = this.raycaster.intersectObjects(this.groundObjects);
            if (intersection_ground.length > 0) {
                const block = intersection_ground[0].object;
                const point = intersection_ground[0].point;
                callback(block.name, point);
            };
        });
    };

    npcs(that, meshNpcs, callback) {
        this.updateNpcObjects(meshNpcs);
        that.input.on('pointerdown', () => {
            const { x, y } = that.getPointer();
            this.raycaster.setFromCamera({ x, y }, that.third.camera);
            const intersection_npc = this.raycaster.intersectObjects(this.npcObjects);
            if (intersection_npc.length > 0) {
                const block = intersection_npc[0].object;
                const point = intersection_npc[0].point;
                callback(block.name, point, block);
            };
        });
    };

    brain(that, meshBrains, callback) {
        this.updateBrainObjects(meshBrains);
        that.input.on('pointerdown', () => {
            const { x, y } = that.getPointer();
            this.raycaster.setFromCamera({ x, y }, that.third.camera);
            const intersection_brain = this.raycaster.intersectObjects(this.brainObjects);
            if (intersection_brain.length > 0) {
                const block = intersection_brain[0].object;
                const point = intersection_brain[0].point;
                callback(block.name, point, block);
            };
        });
    };

    brains(that, callback) {
        that.PhysicsBrains.traverse(child => {
            if (child.isMesh) {
                if (child.name === 'torso') {
                    that.input.on('pointerdown', () => {
                        const { x, y } = that.getPointer();
                        this.raycaster.setFromCamera({ x, y }, that.third.camera);
                        const intersection_brains = this.raycaster.intersectObjects([child]);
                        if (intersection_brains.length > 0) {
                            const block = intersection_brains[0].object;
                            const point = intersection_brains[0].point;
                            callback(block.name, point, block);
                        };
                    });
                };
            };
        });
    };
};

class CreateStraightPath {
    constructor() { };

    computePath(start, end, segments = 20) {
        const points = [];
        for (let i = 0; i <= segments; i++) {
            const t = i / segments;
            const point = new THREE.Vector3().lerpVectors(start, end, t);
            points.push(point);
        }
        return { path: points };
    };
};

class CreateWavePath {
    constructor() { };

    computePath(start, end, options = {}) {
        const {
            segments = 50,
            amplitude = 2,
            frequency = 0.5,
            waveDirection = 'y' // 'x', 'y' or 'z'
        } = options;

        const path = [];
        const direction = new THREE.Vector3().subVectors(end, start);
        const length = direction.length();
        direction.normalize();

        for (let i = 0; i <= segments; i++) {
            const t = i / segments;
            const distance = t * length;
            const waveOffset = Math.sin(t * Math.PI * frequency) * amplitude;

            const point = start.clone().add(
                direction.clone().multiplyScalar(distance)
            );

            // 根据方向添加波浪偏移
            if (waveDirection === 'x') point.x += waveOffset;
            else if (waveDirection === 'y') point.y += waveOffset;
            else point.z += waveOffset;

            path.push(point);
        }

        return { path: path };
    };
};

class CreateNavigation {
    constructor(that, models, debug, callback) {

        const [positions, indices] = getPositionsAndIndices(models);

        const cs = 0.05;
        const ch = 0.05;
        const walkableRadius = 0.2;
        const { success, navMesh } = generateSoloNavMesh(positions, indices, {
            cs,
            ch,
            walkableRadius: Math.round(walkableRadius / ch),
            areas: new Uint8Array([1, 1, 1, 1, 1, 1]), // 1=action, 0=obstacle
        });

        if (success && debug) {
            const navMeshHelper = new NavMeshHelper(navMesh);
            that.third.scene.add(navMeshHelper);
        };

        callback(navMesh);
    };
};

class CreateNavigationPath extends CreateCurve {
    constructor(that, navMesh, startPos, endPos, pathType = 'A*') {

        const straightQuery = new CreateStraightPath();
        const waveQuery = new CreateWavePath();
        const navMeshQuery = new NavMeshQuery(navMesh);
        let type = null;

        if (pathType === 'A*') {
            /** A* path */
            const { path } = navMeshQuery.computePath(startPos, endPos);
            type = path;
        };
        if (pathType === 'straight') {
            /** straight path */
            const { path } = straightQuery.computePath(startPos, endPos);
            type = path;
        };
        if (pathType === 'wave') {
            /** wave path */
            const { path } = waveQuery.computePath(startPos, endPos, { amplitude: .3, frequency: 3 });
            type = path;
        };

        super(that, type, false);
        this.path = type;
        this.pathlen = calculatePathLength(this.spline);
        this.clock = new THREE.Clock();
    };

    init(that, model, key, index) {
        const status = [
            { PROGRESS: PROGRESS, CONSTANT_VELOCITY: CONSTANT_VELOCITY },
            { PROGRESS: PROGRESS_PUMPKIN, CONSTANT_VELOCITY: CONSTANT_VELOCITY_PUMPKIN },
            { PROGRESS: PROGRESS_GHOST, CONSTANT_VELOCITY: CONSTANT_VELOCITY_GHOST },
        ];

        if (that[key.progress] >= this.pathlen - .5) {
            that[key.whoWatchOpen].value = false;
            that[key.whoMove] = null;
            that[key.progress] = status[index].PROGRESS;
            that[key.velocity] = status[index].CONSTANT_VELOCITY;
            return;
        };

        that[key.progress] += this.clock.getDelta() * that[key.velocity];
        that[key.progress] = that[key.progress] % this.pathlen;
        const point = getPointAtDistance(this.spline, that[key.progress]);
        model.position.copy(point);

        const nextPoint = getPointAtDistance(this.spline, that[key.progress] + .1);

        let offsetAngle = -3.2;
        let mtx = new THREE.Matrix4();
        mtx.lookAt(model.position, nextPoint, model.up);
        mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)));
        let toRot = new THREE.Quaternion().setFromRotationMatrix(mtx);
        model.quaternion.slerp(toRot, 0.2);

    };

};

class CreateInterstellarCoordinates {
    lightPyramidArr = [];
    constructor(that, pos, height, width, callback) {
        const coneGeometry = new THREE.ConeGeometry(width, height, 4);
        const material_line = new THREE.MeshBasicMaterial({
            color: 0x00ffff
        });
        const material_geo = new THREE.MeshPhongMaterial({
            color: 0x004444,
            transparent: true,
            opacity: 0.5
        });

        const bottomCone = new THREE.Mesh(coneGeometry, material_geo);

        const wireframe = new THREE.EdgesGeometry(bottomCone.geometry);
        const wireframeMesh = new THREE.LineSegments(wireframe, material_line);
        bottomCone.add(wireframeMesh);

        bottomCone.position.copy(pos);
        bottomCone.rotateX(-Math.PI / 2);
        bottomCone.position.z += height / 2;

        const topCone = new THREE.Mesh(coneGeometry, material_geo);

        const wireframe_top = new THREE.EdgesGeometry(topCone.geometry);
        const wireframeMesh_top = new THREE.LineSegments(wireframe_top, material_line);
        topCone.add(wireframeMesh_top);

        topCone.scale.y = 0.5;
        topCone.position.copy(pos);
        topCone.rotateX(Math.PI / 2);
        topCone.position.z += height + height * 0.25;

        const plane = new THREE.PlaneGeometry(height / 2, height / 2);
        const markerMaterial = new THREE.MeshBasicMaterial({
            side: THREE.DoubleSide,
            color: 0x00ffff,
            transparent: true,
            depthTest: false
        })

        const ring = new THREE.Mesh(plane, markerMaterial);

        const wireframe_ring = new THREE.EdgesGeometry(ring.geometry);
        const wireframeMesh_ring = new THREE.LineSegments(wireframe_ring, material_line);
        ring.add(wireframeMesh_ring);

        ring.position.copy(pos);
        ring.position.z += height;
        const lightPyramidGroup = new THREE.Group();
        lightPyramidGroup.rotateX(-Math.PI / 2);
        lightPyramidGroup.add(bottomCone, topCone, ring);
        /** model image */
        that.lightPyramidGroupImage = lightPyramidGroup;

        that.third.scene.add(lightPyramidGroup);
        this.lightPyramidArr.push(lightPyramidGroup);
        callback(true);
    };

    anima() {
        this.lightPyramidArr.map(group => {
            group.children[0].rotation.y += 0.01;
            group.children[1].rotation.y -= 0.01;
            let scale = group.children[2].scale.x;
            let range = 3;
            scale += 0.02;
            let opacity;
            if (scale < range * 0.3) {
                opacity = (scale - 1) / (range * 0.3 - 1);
            }
            else if (scale > range * 0.3 && scale <= range) {
                opacity = 0.3 - (scale - range * 0.3) / (range - range * 0.3);
            }
            else {
                scale = 1.0;
            }
            group.children[2].scale.set(scale, scale, scale);
            group.children[2].material.opacity = opacity;
        });
    };
};

class CreateCameraDamping {
    static init(that, orb, name) {
        that[name] = orb;
        orb.enableZoom = false;
        orb.enablePan = false;
        orb.enableDamping = true;
        orb.dampingFactor = 0.06;
        orb.maxPolarAngle = Math.PI / 3.2;
        orb.minPolarAngle = Math.PI / 3.2;
    };
};

class CreateSkyBox extends Http {
    constructor(that) {
        super();
        const loader = new THREE.CubeTextureLoader();
        loader.setPath(this.http + this.path_img);
        loader.load(["city_sky_px.png", "city_sky_nx.png", "city_sky_py.png", "city_sky_ny.png", "city_sky_pz.png", "city_sky_nz.png"], function (texture) {
            that.third.scene.background = texture;
        });
    };
};

class SeaWater extends Http {
    constructor(that, arr) {
        super();
        this.init(that, arr);
    };

    async init(that, arr) {
        const textures = await Promise.all([
            that.third.load.texture(this.http + this.path_img + arr[0]),
            that.third.load.texture(this.http + this.path_img + arr[1])
        ]);
        textures[0].needsUpdate = true;
        textures[1].needsUpdate = true;
        that.third.misc.water({
            width: 7.2,
            height: 5,
            x: -1.5,
            y: -1.2,
            z: 7.8,
            normalMap0: textures[0],
            normalMap1: textures[1]
        });
    };
};

class CreatePhysicsGameWorldBiology extends Http {
    // FishBones
    constructor(that, third) {
        super();
        this.physicsScene = new ExtendedObject3D();
        this.physicsPlayer = new ExtendedObject3D();
        this.physicsBrains = new ExtendedObject3D();
        this.physicsBrain = new ExtendedObject3D();
        this.physicsPumpkin = new ExtendedObject3D();
        this.physicsFloor = new ExtendedMesh();
        this.that = that;
        this.third = third;
        this.Ghost = null;
        this.Brain = null;
    };

    async initGhostModel() {
        return new Promise(async (resolve, reject) => {
            try {
                this.Ghost = await this.third.load.gltf(this.http + this.path_models + 'character-ghost.glb');
                resolve();
            } catch (error) {
                reject(error);
            };
        })
    };

    async initBrainModel() {
        return new Promise(async (resolve, reject) => {
            try {
                this.Brain = await this.third.load.gltf(this.http + this.path_models + 'brain.glb');
                resolve();
            } catch (error) {
                reject(error);
            };
        });
    };

    async world() {
        return new Promise(async (resolve, reject) => {
            const GameWorldScene = await this.third.load.gltf(this.http + this.path_models + 'GameWorldScene.glb');
            const GameWorldScenePhysics = this.physicsScene.add(GameWorldScene.scene);
            GameWorldScenePhysics.position.set(2.6, -2, -1.1);
            GameWorldScenePhysics.traverse(child => {
                /** @abstract Mesh */
                if (child.isMesh) {
                    /** @abstract Player action area */
                    if (child.name.indexOf('Mesh_floor')) {
                        child.material = true ? child.material : DebugModelsMaterial.init();
                        this.that.GameWorldScenePhysicsElement.push({
                            name: child.name,
                            mesh: child
                        });
                    };

                    /** @abstract Player obstacle area */
                    if (child.name.indexOf('Mesh_beach')) {
                        child.material = true ? child.material : DebugModelsMaterial.init();
                        this.that.GameWorldScenePhysicsElement.push({
                            name: child.name,
                            mesh: child
                        });
                    };
                    if (child.name.indexOf('Mesh_mine')) {
                        child.material = true ? child.material : DebugModelsMaterial.init();
                        this.that.GameWorldScenePhysicsElement.push({
                            name: child.name,
                            mesh: child
                        });
                    };
                    if (child.name.indexOf('Mesh_beach')) {
                        child.material = true ? child.material : DebugModelsMaterial.init();
                        this.that.GameWorldScenePhysicsElement.push({
                            name: child.name,
                            mesh: child
                        });
                    };
                    if (child.name.indexOf('Mesh_grass')) {
                        child.material = true ? child.material : DebugModelsMaterial.init();
                        this.that.GameWorldScenePhysicsElement.push({
                            name: child.name,
                            mesh: child
                        });
                    };
                    if (child.name.indexOf('iron-fence')) {
                        child.material = true ? child.material : DebugModelsMaterial.init();
                        this.that.GameWorldScenePhysicsElement.push({
                            name: child.name,
                            mesh: child
                        });
                    };
                    if (child.name.indexOf('crypt-small')) {
                        child.material = true ? child.material : DebugModelsMaterial.init();
                        this.that.GameWorldScenePhysicsElement.push({
                            name: child.name,
                            mesh: child
                        });
                    };
                };
            });
            this.third.add.existing(GameWorldScenePhysics);
            resolve();
        });
    };

    async player() {
        const Player = await this.third.load.gltf(this.http + this.path_models + 'character-vampire.glb');
        this.that.PlayerPhysics = this.physicsPlayer.add(Player.scene);
        this.that.PlayerPhysics.position.set(this.that.PlayerPhysicsData.coordinate.x, -.9, this.that.PlayerPhysicsData.coordinate.y);
        this.that.PlayerPhysics.uuid = this.that.PlayerPhysicsData.playerDataType.id;
        this.that.PlayerPhysics.hp = this.that.PlayerPhysicsData.playerDataType.hp;
        this.third.scene.add(this.that.PlayerPhysics);
        this.third.animationMixers.add(this.that.PlayerPhysics.anims.mixer);
        Player.animations.forEach(clip => {
            this.that.PlayerAnimaMap.push(this.that.PlayerPhysics.anims.mixer.clipAction(clip));
        });
        this.that.PlayerAnimaMap[1].reset().play();
    };

    async npc() {
        return new Promise(async (resolve, reject) => {
            if (ZOMB_COORD && ZOMB_COORD.coordinate.length >= 0) {
                let GhostPhysics = [];
                await Promise.all(ZOMB_COORD.coordinate.map(async (item, index) => {
                    const mesh = this.Ghost.scene.clone();
                    const ghostItems = new ExtendedObject3D().add(mesh);
                    ghostItems.position.set(item.coordinate.x, -.9, item.coordinate.z);
                    ghostItems.rotation.y = Math.floor(Math.random() * 5);
                    ghostItems.uuid = ZOMB_COORD.zombie[index].uuid;

                    GhostPhysics.push({
                        name: item.name,
                        coordinate: item,
                        data: ZOMB_COORD.zombie[index],
                        example: ghostItems,
                        anima: this.Ghost,
                        play: [],
                        mesh: []
                    });

                    this.third.physics.add.existing(ghostItems, {
                        mass: 1,
                        collisionFlags: 2,
                        shape: 'box'
                    });
                    ghostItems.body.setGravity(0, 0, 0);
                    this.third.scene.add(ghostItems);

                    this.third.animationMixers.add(ghostItems.anims.mixer);

                    if (GhostPhysics.length === ZOMB_COORD.coordinate.length) {
                        GhostPhysics.forEach(item => {
                            CreateNpcStatus.init(this.that, { x: item.coordinate.coordinate.x, z: item.coordinate.coordinate.z }, item.data);
                            item.anima.animations.forEach(clip => {
                                item.play.push(item.example.anims.mixer.clipAction(clip));
                            });
                            item.example.traverse(child => {
                                if (child.isMesh) {
                                    child.uuid = item.data.uuid;
                                    if (child.name === 'torso') {
                                        item.mesh.push({
                                            name: child.name,
                                            mesh: child
                                        });
                                    };
                                };
                            });
                        });

                        GhostPhysics.forEach(item => {
                            item.play[1].reset().play();
                        });
                        resolve(GhostPhysics);
                    };
                }));
            } else {
                reject([]);
            };
        });
    };

    async brains() {
        const Brains = await this.third.load.gltf(this.http + this.path_models + 'character-skeleton.glb');
        this.that.PhysicsBrains = this.physicsBrains.add(Brains.scene);
        this.that.PhysicsBrains.position.set(-6.9, -.9, 5.6);
        this.that.PhysicsBrains.rotation.y = Math.PI / 2;
        this.third.scene.add(this.that.PhysicsBrains);
        this.third.animationMixers.add(this.that.PhysicsBrains.anims.mixer);
        Brains.animations.forEach(clip => {
            this.that.BrainsAnimaMap.push(this.that.PhysicsBrains.anims.mixer.clipAction(clip));
        });
        this.that.BrainsAnimaMap[1].reset().play();
    };

    async brain(weight) {
        const colors = {
            waste: 0x808080,
            ordinary: 0xffffff,
            rare: 0x0000ff,
            epic: 0xf800080,
            legend: 0xff0000
        };

        this.that.floorCircle.position.set(0, -.5, 0);
        this.that.floorCircle.body.needUpdate = true;
        weight.map(str => {
            const GHOST_RANDOM_RADIUS = Math.sqrt(Math.random()) * 1.5;
            const GHOST_ANGLE_STEP = Math.random() * Math.PI * 2;

            const X = ATTACK_LOCK_GHOST_POS.x + GHOST_ANGLE_STEP * Math.cos(GHOST_RANDOM_RADIUS);
            const Z = ATTACK_LOCK_GHOST_POS.z + GHOST_ANGLE_STEP * Math.sin(GHOST_RANDOM_RADIUS);
            const mesh = this.Brain.scene.clone();
            const brainItem = new ExtendedObject3D().add(mesh);
            brainItem.uuid = JSON.parse(str).uuid;
            brainItem.scale.set(.016, .016, .016);
            brainItem.rotation.y = Math.floor(Math.random() * 5);
            brainItem.position.set(X, 2, Z);
            brainItem.traverse((child) => {
                if (child.isMesh) {
                    child.material = new THREE.MeshPhongMaterial({
                        color: colors[JSON.parse(str).type] || colors.ordinary,
                        transparent: true,
                        opacity: 0.5,
                    });
                    child.uuid = brainItem.uuid;
                };
            });
            this.third.physics.add.existing(brainItem, {
                mass: 1,
                shape: 'box'
            });

            brainItem.body.setRestitution(0.1);
            this.third.scene.add(brainItem);
            this.that.brainItems.push(brainItem);
        });

    };

    async ship() {
        const ship = await this.third.load.fbx(this.http + this.path_models + 'ship-large.fbx');
        ship.scale.set(.003, .003, .003);
        ship.position.set(.5, -1.5, 8);
        this.third.scene.add(ship);
    };

    async pumpkinWeapon() {
        const Pumpkin = await this.third.load.gltf(this.http + this.path_models + 'pumpkin.glb');
        this.that.PhysicsPumpkin = this.physicsPumpkin.add(Pumpkin.scene);
        this.that.PhysicsPumpkin.position.set(0, 30, 0);
        this.third.scene.add(this.that.PhysicsPumpkin);
    };

};

class CreateNpcStatus {
    static init(that, coordinate, data) {
        const geometry = new THREE.PlaneGeometry(1, 0.1);
        const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
        const bloodBar = new THREE.Mesh(geometry, material);
        bloodBar.uuid = data.uuid;
        bloodBar.position.set(coordinate.x, .2, coordinate.z);
        bloodBar.scale.set(data.hp / data.hp, data.hp / data.hp, data.hp / data.hp);
        that.NPCBloodBars.push(bloodBar);
        that.third.scene.add(bloodBar);
    };

    static updateStatus(bloodBar, health) {
        const scaleX = health;
        bloodBar.scale.x = scaleX;

        if (bloodBar.scale.x > 1) {
            bloodBar.traverse(child => {
                if (child.isMesh) {
                    child.material = new THREE.MeshPhysicalMaterial({ color: "#ffa500", side: THREE.DoubleSide });
                };
            });
        };

        if (bloodBar.scale.x <= .6) {
            bloodBar.traverse(child => {
                if (child.isMesh) {
                    child.material = new THREE.MeshPhysicalMaterial({ color: "#c4c400", side: THREE.DoubleSide });
                };
            });
        };

        if (bloodBar.scale.x <= .2) {
            bloodBar.traverse(child => {
                if (child.isMesh) {
                    child.material = new THREE.MeshPhysicalMaterial({ color: "#bebebe", side: THREE.DoubleSide });
                };
            });
        };

        console.log('进入战斗！')
        GO_INTO_ACTION = true;
        HtmlSubscription.publish('fight', true);
    };

};

class CreateAttackRange {

    player(show) {
        const geometry = new THREE.CircleGeometry(4, 32);
        const material = new THREE.MeshBasicMaterial({
            color: 0x00ff00,
            transparent: true,
            opacity: show ? 0.5 : 0
        });
        this.playerCircle = new THREE.Mesh(geometry, material);
        this.playerCircle.rotation.x = Math.PI / 2; // if not show plase set -2

        this.third.physics.add.existing(this.playerCircle, {
            mass: 1,
            collisionFlags: 2,
            shape: 'mesh'
        });
        this.playerCircle.body.setGravity(0, 0, 0);

        this.third.scene.add(this.playerCircle);
        this.openPlayerAR = true;
    };

    playerWarningArea(show) {
        const geometry = new THREE.CircleGeometry(1, 32);
        const material = new THREE.MeshBasicMaterial({
            color: 0xff0000,
            transparent: true,
            opacity: show ? 0.5 : 0
        });
        this.playerCircleWA = new THREE.Mesh(geometry, material);
        this.playerCircleWA.rotation.x = Math.PI / 2;

        this.third.physics.add.existing(this.playerCircleWA, {
            mass: 1,
            collisionFlags: 2,
            shape: 'mesh'
        });
        this.playerCircleWA.body.setGravity(0, 0, 0);

        this.third.scene.add(this.playerCircleWA);
        this.openPlayerARForWA = true;
    };

    npc(show) {

    };

    airWall(show) {
        const geometry = new THREE.BoxGeometry(4, 6, -2);
        const material = new THREE.MeshBasicMaterial({
            color: 0xff0000,
            transparent: true,
            opacity: show ? 0.5 : 0
        });
        this.airWall = new THREE.Mesh(geometry, material);
        this.airWall.rotation.x = Math.PI / 2;
        this.airWall.position.set(-6.8, 0, 5.5);

        this.third.physics.add.existing(this.airWall, {
            mass: 1,
            collisionFlags: 2,
            shape: 'box'
        });
        this.airWall.body.setGravity(0, 0, 0);

        this.third.scene.add(this.airWall);
    };

    pumpkinWeapon(show) {
        const geometry = new THREE.CircleGeometry(.4, 32);
        const material = new THREE.MeshBasicMaterial({
            color: 0xFF0000,
            transparent: true,
            opacity: show ? 0.5 : 0
        });
        this.pumpkinWeaponCircle = new THREE.Mesh(geometry, material);
        this.pumpkinWeaponCircle.rotation.x = Math.PI / 2;

        this.third.physics.add.existing(this.pumpkinWeaponCircle, {
            mass: 1,
            collisionFlags: 2,
            shape: 'mesh'
        });
        this.pumpkinWeaponCircle.body.setGravity(0, 0, 0);

        this.third.scene.add(this.pumpkinWeaponCircle);
        this.openPumpkinWeaponAR = true;
    };

    floor() {
        const geometry = new THREE.BoxGeometry(0, 0);
        this.floorCircle = new THREE.Mesh(geometry);
        this.floorCircle.rotation.x = Math.PI / -2;
        this.floorCircle.position.set(0, 30, 0);

        this.third.physics.add.existing(this.floorCircle, {
            mass: 0,
            collisionFlags: 2,
            shape: 'box',
            width: 20,
            height: 20,
            depth: .1
        });

        this.floorCircle.body.setRestitution(0.1);
        this.third.scene.add(this.floorCircle);

    };
};

/**
 * @class CreateSkillList
 * @description skill list *Q: Tracking attacks. *W: normal attack.
 */
class CreateSkillList {
    Q() {
        this.input.keyboard.on('keydown-Q', () => {
            if (this.findNPCImgMark.position.y === 30) return this.io.getAdvertiseSceneGameMessage('1');
            const pos = this.GhostMarkPhysics.exa.position;
            const uuid = this.GhostMarkPhysics.uuid;
            if (CreateCombatSystem.myLinkedList.findItem(uuid) === null) {
                if (this.findNPCImgMark.position.y === 30) return this.io.getAdvertiseSceneGameMessage('1');
                this.io.getAdvertiseSceneGameMessage('2');
            } else {
                ATTACK_LOCK_GHOST_UUID = uuid;
                ATTACK_LOCK_GHOST_POS = pos;
                CreateSkillQ.main(this);
            };
        });
    };

};

/**
 * @class CreateSkillQ
 * @description special skill - track
 */
class CreateSkillQ {
    static Q(that) {
        if (that.OpenPumpkinWeaponMove) return delete CreateSkillQ.instance;
        CreateBiologyActAnima.playerAttack.call(that);
        CreateSkillQ.CreatePumpkinWeapon(that);
        HtmlSubscription.publish("skill_Q", 1);
    };

    static CreatePumpkinWeapon(that) {
        if (that.pumpkinWeaponMove) {
            that.pumpkinWeaponMove = null;
            that.progress_pumpkin = PROGRESS_PUMPKIN;
            that.velocity_pumpkin = CONSTANT_VELOCITY_PUMPKIN;
        };
        that.pumpkinWeaponMove = new CreateNavigationPath(that, that.GroundNavMesh, that.PlayerPhysics.position, that.ghostPos, 'wave');
        that.OpenPumpkinWeaponMove = true;
    };

    static main(that) {
        if (!CreateSkillQ.instance) {
            CreateSkillQ.instance = new CreateSkillQ();
            CreateSkillQ.Q(that);
        };
        return CreateSkillQ.instance;
    };
};

/**
 * @class CreateZombiesSpeakTalk
 * @description Zombie speech copywriting
 */
class CreateZombiesSpeakTalk {
    static talk() {
        socket.emit('SceneGameZombieChat', {
            type: 'talk',
            body: 'Copywriting-one'
        });
    };

    static main() {
        if (!CreateZombiesSpeakTalk.instance) {
            CreateZombiesSpeakTalk.instance = new CreateZombiesSpeakTalk();
            CreateZombiesSpeakTalk.talk();
        };
        return CreateZombiesSpeakTalk.instance;
    };
};

/**
 * @class CreateZombiesSpeakAskFor
 * @description Zombie speech copywriting
 */
class CreateZombiesSpeakAskFor {
    static askFor() {
        socket.emit('SceneGameZombieChat', {
            type: 'talk',
            body: 'Copywriting-two'
        });
    };

    static main() {
        if (!CreateZombiesSpeakAskFor.instance) {
            CreateZombiesSpeakAskFor.instance = new CreateZombiesSpeakAskFor();
            CreateZombiesSpeakAskFor.askFor();
        };
        return CreateZombiesSpeakAskFor.instance;
    };
};

class CreateCombatSystem {
    static myLinkedList = new LinkedList();
    static ghost = null;
    static listenToGhost(npcs) {
        CreateCombatSystem.ghost = npcs;
        npcs.map(item => {
            /** Listen player and ghost */
            this.third.physics.add.collider(this.playerCircle, item.exa, collisionInfo => {
                if (collisionInfo === 'start') {
                    CreateCombatSystem.myLinkedList.append(JSON.stringify({ id: item.uuid, ghost: [] }));
                    // CreateCombatSystem.myLinkedList.print();
                };
                if (collisionInfo === 'end') {
                    CreateCombatSystem.myLinkedList.removeItem(item.uuid);
                    // CreateCombatSystem.myLinkedList.print();
                };
            });

            /** Listen pumpkinWeapon and ghost */
            this.third.physics.add.collider(this.pumpkinWeaponCircle, item.exa, collisionInfo => {
                if (collisionInfo === 'start') {
                    this.OpenPumpkinWeaponMove = false;
                    this.PhysicsPumpkin.position.set(0, 30, 0);
                    if (item.exa.uuid !== ATTACK_LOCK_GHOST_UUID) return;
                    item.exa.traverse(child => {
                        if (child.isMesh) {
                            if (child.name === "arm-left" || child.name === "arm-right") {
                                const originalColor = child.material.color.clone();
                                child.material = new THREE.MeshPhysicalMaterial({ color: 0xff0000, side: THREE.DoubleSide });
                                const timer = setTimeout(() => {
                                    child.material.color.copy(originalColor);;
                                    clearTimeout(timer);
                                }, 100);
                            };
                        };
                    });

                    this.ghostPos = item.exa.position;
                    this.GhostPhysicsItem = item.exa;
                    this.GhostPhysicsItemAnima = item.play;

                    CreateBiologyActAnima.ghostBeHit(item);
                    socket.emit('privateSceneGameGhostBloodBar', JSON.stringify({ type: 'BC', body: ATTACK_LOCK_GHOST_UUID }));
                };
            });

        });
    };

    static markGhost(that, uuid) {
        that.GhostMarkPhysics = {
            uuid: uuid,
            exa: CreateCombatSystem.ghost.find(item => item.uuid === uuid).exa
        };
        that.openPlayerFindGhostMark = true;
        return CreateCombatSystem.myLinkedList.findItem(uuid);
    };
};

class CreateAIModelSystem {

    listenOpenAISystem() {
        this.third.physics.add.collider(this.playerCircleWA, this.airWall, collisionInfo => {
            if (collisionInfo === 'start') {
                this.openAISys = true;
                this.findNPCImgWho.position.set(this.PhysicsBrains.position.x, this.PhysicsBrains.position.y + 1.2, this.PhysicsBrains.position.z);

                if (CreateZombiesSpeakAskFor.instance) delete CreateZombiesSpeakAskFor.instance;
                CreateZombiesSpeakTalk.talk();
            };
            if (collisionInfo === 'end') {
                this.openAISys = false;
                this.findNPCImgWho.position.set(0, 30, 0);

                if (CreateZombiesSpeakTalk.instance) delete CreateZombiesSpeakTalk.instance;
                CreateZombiesSpeakAskFor.askFor();
            };
        });
    };

};

class CreateListenPhysicsGameWorldBiology {
    static listenPlayer() {
        this.watchOpenPlayerMove = this.watch(this.OpenPlayerMove, (o, n) => {
            this.OpenPlayerMove = n;
            if (this.OpenPlayerMove) {
                CreateBiologyActAnima.playerMove.call(this);
            } else {
                CreateBiologyActAnima.playerStop.call(this);
            };
        });
    };

    static listenPumpkinWeapon() {
        this.watchOpenPumpkinWeaponMove = this.watch(this.OpenPumpkinWeaponMove, (o, n) => {
            this.OpenPumpkinWeaponMove = n;
            this.PhysicsPumpkin.position.set(0, 30, 0);
        });
    };

    static listenGhost() {
        this.watchOpenGhostMove = this.watch(this.OpenGhostMove, (o, n) => {
            this.OpenGhostMove = n;
            if (this.OpenGhostMove) {
                CreateBiologyActAnima.ghostMove.call(this);
            } else {
                CreateBiologyActAnima.ghostStop.call(this);
            };
        });
    }
};


let bubble = null;
let mark = null
let who = null;
class CreateSceneImage extends Http {

    async PlayerEmoji() {
        const atlas = await this.third.load.texture(this.http + this.path_img + 'emote_exclamation.png');
        bubble = new FLAT.Button(atlas, 2, 3, 0);
        bubble.setInteractive();
        bubble.name = 'player-img-question';
        bubble.position.set(0, 30, 0);
        bubble.setScale(.015);
        bubble.flipX(true);
        this.third.scene.add(bubble);
        return bubble;
    };

    async GhostMark() {
        const atlas = await this.third.load.texture(this.http + this.path_img + 'platformPack_tile022.png');
        mark = new FLAT.Button(atlas, 2, 3, 0);
        mark.setInteractive();
        mark.name = 'npc-img-mark';
        mark.position.set(0, 30, 0);
        mark.setScale(.015);
        mark.flipX(true);
        this.third.scene.add(mark);
        return mark;
    };

    async BrainsEmoji() {
        const atlas = await this.third.load.texture(this.http + this.path_img + 'emote_question.png');
        who = new FLAT.Button(atlas, 2, 3, 0);
        who.setInteractive();
        who.name = 'npc-img-who';
        who.position.set(0, 30, 0);
        who.setScale(.015);
        who.flipX(true);
        this.third.scene.add(who);
        return who;
    };

};

class CreateExecuteSceneImage {
    static playerEmoji(that) {
        that.watchOpenPlayerMove.value = false;
        that.openPlayerFindNPCQuestion = true;
        const time = setTimeout(() => {
            that.openPlayerFindNPCQuestion = false;
            bubble.position.set(0, 30, 0);
            delete CreateExecuteSceneImage.instance;
            clearTimeout(time);
        }, 3000);
    };

    static main(that) {
        if (!CreateExecuteSceneImage.instance) {
            CreateExecuteSceneImage.instance = new CreateExecuteSceneImage();
            CreateExecuteSceneImage.playerEmoji(that);
        };
        return CreateExecuteSceneImage.instance;
    };
};

class CreateEquipmentDropSystem {
    static weight(uuid) {
        HtmlSubscription.publish("killGhostEDWeight", uuid);
    };

    static main(uuid) {
        if (!CreateEquipmentDropSystem.instance) {
            CreateEquipmentDropSystem.instance = new CreateEquipmentDropSystem();
            CreateEquipmentDropSystem.weight(uuid);
        };
        return CreateEquipmentDropSystem.instance;
    };
};

class CreateBiologyActAnima {
    static playerAttack() {
        this.PlayerAnimaMap[10].setLoop(THREE.LoopOnce);
        this.PlayerAnimaMap[10].reset().play();
    };

    static playerMove() {
        this.PlayerAnimaMap[1].stop();
        this.PlayerAnimaMap[2].reset().play();
    };

    static playerStop() {
        this.PlayerAnimaMap[2].stop();
        this.PlayerAnimaMap[1].reset().play();
    };

    static ghostBeHit(item) {
        item.play[7].setLoop(THREE.LoopOnce);
        item.play[7].reset().play();
    };

    static ghostDie(item) {
        return new Promise((resolve, reject) => {
            item.play[9].setLoop(THREE.LoopOnce);
            item.play[9].clampWhenFinished = true;
            item.play[9].reset().play();
            let timer = setTimeout(() => {
                clearTimeout(timer);
                resolve();
            }, 1000);
        });
    };

    static ghostMove() {
        this.GhostPhysicsItemAnima[1].stop();
        this.GhostPhysicsItemAnima[2].reset().play();
    };

    static ghostStop() {
        this.GhostPhysicsItemAnima[2].stop();
        this.GhostPhysicsItemAnima[1].reset().play();
    };

    static ghostDisappear(item) {
        return new Promise((resolve, reject) => {
            gsap.to(item.example.position, {
                y: -1.2,
                duration: 2,
                ease: "power2.out",
                onUpdate: () => {
                    item.example.body.needUpdate = true;
                },
                onComplete: () => {
                    resolve();
                }
            });
        });
    };
};


export default class Game extends Scene3D {

    constructor() {
        super({
            key: 'Game'
        });

        this.io = null;

        this.http = `${proxy.development.API}`;
        this.path_img = `${proxy.development.PATH_IMG}`;
        this.path_models = `${proxy.development.PATH_MODELS}`;

        this.ui = null;

        this.NPCBloodBars = [];
        this.NPCImage = null;

        this.openPlayerAR = false;
        this.playerCircle = null;
        this.playerCircleWA = null;

        this.openPumpkinWeaponAR = false;
        this.pumpkinWeaponCircle = null;

        this.progress = PROGRESS;
        this.velocity = CONSTANT_VELOCITY;

        this.progress_pumpkin = PROGRESS_PUMPKIN;
        this.velocity_pumpkin = CONSTANT_VELOCITY_PUMPKIN;

        this.progress_ghost = PROGRESS_GHOST;
        this.velocity_ghost = CONSTANT_VELOCITY_GHOST;

        this.orbitControls = null;

        this.findNPCImgQuestion = null;
        this.findNPCImgMark = null;
        this.findNPCImgWho = null;

        this.openPlayerFindNPCQuestion = false;
        this.openPlayerFindGhostMark = false;
        this.OpenPlayerMove = false;

        this.PlayerMove = null;
        this.PlayerPhysics = null;
        this.PhysicsBrains = null;
        this.PhysicsPumpkin = null;

        this.pumpkinWeaponMove = null;
        this.OpenPumpkinWeaponMove = false;
        this.watchOpenPumpkinWeaponMove = null;

        this.bloodBarMove = null;
        this.GhostPhysicsItem = null;
        this.GhostMove = null;
        this.OpenGhostMove = false;
        this.watchOpenGhostMove = null;

        this.GhostMarkPhysics = null;
        this.GhostPhysics = null;
        this.biology = null;
        this.watchOpenPlayerMove = null;

        this.BrainPhysics = null;

        this.GroundNavMesh = null;
        this.CreateInterstellarCoordinates = null;
        this.OpenCreateInterstellarCoordinates = false;

        this.GameWorldScenePhysicsElement = [];
        this.PlayerAnimaMap = [];
        this.BrainsAnimaMap = [];

        this.meshNpcs = [];
        this.phyNpcs = [];

        this.BrainItems = null;
        this.BrainIndex = null;
        this.brainItems = [];

        this.OpenStartGameLookScene = true;

        this.openAISys = false;

    };

    async init() {
        this.accessThirdDimension();

        /**@description Events */
        socket.on('privateSceneGameMonstersGhost', async obj => {
            if (ZOMB_COORD) {
                ZOMB_COORD = obj;
                this.resetSceneGhostSystem();
            } else {
                ZOMB_COORD = obj;
            };
        });

        /**@description Events */
        socket.on('ghostBloodBarCount', obj => {

            if (obj.count <= 0) return this.killGhostItem(obj.uuid);
            this.bloodBar = this.NPCBloodBars.find(item => item.uuid === obj.uuid);
            CreateNpcStatus.updateStatus(this.bloodBar, obj.count);

            HtmlSubscription.publish('ai_run', obj.count);

        });

        /**@description Events */
        socket.on('operateGhostTaskStatus', status => {
            status === 'start' ? HtmlSubscription.publish("ghost_refresh_task", false) : null;
        });

        /**@abstract Events */
        socket.on('EquipmentPicking', obj => {
            HtmlSubscription.publish('ep-count', obj.count);
            this.third.scene.remove(this.BrainItems);
            this.brainItems.splice(this.BrainIndex, 1);
        });

        /**@abstract Events */
        socket.on('SceneGame2DGetBall', obj => {
            HtmlSubscription.publish('game_2d_scene_res_ball');
            HtmlSubscription.publish('kg-count', obj.count);
        });

        /**@abstract Events */
        socket.on('SceneGameKillGhostCount', obj => {
            HtmlSubscription.publish('kg-count', obj.count);
        });

    };

    async preload() { };

    async create() {

        // this.third.physics.debug.enable();

        const skillList = new CreateSkillList();
        skillList.Q.call(this);

        this.io = new SocketRoomData();
        this.io.getAdvertiseMessage();
        this.io.getZombieCoordinates();

        window.localStorage.getItem('GameOption') === 'new' ? this.PlayerPhysicsData = await this.io.getSceneGamePlayerInit('new') : this.PlayerPhysicsData = await this.io.getSceneGamePlayerInit('read');

        const hdr = new HDR();
        hdr.init.call(this);

        new SeaWater(this, [
            'Water_1_M_Normal.jpg',
            'Water_2_M_Normal.jpg'
        ]);

        this.AR = new CreateAttackRange();
        this.AR.player.call(this, false);
        this.AR.playerWarningArea.call(this, false);
        this.AR.airWall.call(this, false);
        this.AR.pumpkinWeapon.call(this, false);
        this.AR.floor.call(this);

        this.findNPCImage = new CreateSceneImage();
        this.findNPCImgQuestion = await this.findNPCImage.PlayerEmoji.call(this);
        this.findNPCImgMark = await this.findNPCImage.GhostMark.call(this);
        this.findNPCImgWho = await this.findNPCImage.BrainsEmoji.call(this);

        await initRecastNavigation();

        const { orbitControls } = await this.third.warpSpeed('-sky', '-ground');

        CreateCameraDamping.init(this, orbitControls, 'orbitControls');

        new CreateSkyBox(this);

        this.cameraLookSandTable();

        this.biology = new CreatePhysicsGameWorldBiology(this, this.third);
        await this.biology.initGhostModel();
        await this.biology.initBrainModel();
        await this.biology.world();
        await this.biology.brains();
        this.biology.player();
        this.biology.ship();
        this.biology.pumpkinWeapon();
        this.GhostPhysics = await this.biology.npc();

        new CreateNavigation(this, [
            ...this.GameWorldScenePhysicsElement.map(item => {
                /**@abstract use Player action area or obstacle area */
                return item.mesh
            })
        ], false, navMesh => {
            this.GroundNavMesh = navMesh;
        });

        this.CreateInterstellarCoordinates = new CreateInterstellarCoordinates(this, new THREE.Vector3(0, 0, 29), 1, .3, bol => {
            if (bol) {
                this.OpenCreateInterstellarCoordinates = true;
            };
        });

        this.meshNpcs = [];
        this.phyNpcs = [];
        this.GhostPhysics.forEach(it => {
            this.phyNpcs.push({
                uuid: it.data.uuid,
                exa: it.example,
                play: it.play
            });
            it.mesh.map(ite => {
                this.meshNpcs.push(ite.mesh);
            });
        });
        this.ray = new CreateRayModel();
        /**
         * @description set ray floor used for player move
         */
        this.ray.ground(this, [
            ...this.GameWorldScenePhysicsElement.map(item => {
                /**@abstract use Player action area or obstacle area */
                return item.mesh
            })
        ], (name, pos, obj) => {
            if (name === 'Mesh_floor' || name === 'Mesh_floor_1' || name === 'Mesh_floor_2' || name === 'Mesh_floor_3' || name === 'Mesh_floor_4' || name === 'Mesh_floor_5') {
                this.CreateInterstellarCoordinates.lightPyramidArr[0].position.set(pos.x, -28, pos.z);
                if (this.PlayerMove) {
                    this.PlayerMove = null;
                    this.progress = PROGRESS;
                    this.velocity = CONSTANT_VELOCITY;
                };
                this.PlayerMove = new CreateNavigationPath(this, this.GroundNavMesh, this.PlayerPhysics.position, pos);
                this.watchOpenPlayerMove.value = true;
            };
        });

        /**
         * @description set ray ghost used for attacking ghosts
         */
        this.ray.npcs(this, this.meshNpcs, (name, pos, obj) => {
            if (name === 'torso') {
                if (!GO_INTO_ACTION) {
                    this.ghostPos = pos;
                    if (CreateCombatSystem.markGhost(this, obj.uuid) === null) {
                        CreateExecuteSceneImage.main(this);
                    };
                };
            };
        });

        /**
         * @description set ray brain pick up items
         */
        this.ray.brain(this, this.brainItems, (name, pos, obj) => {
            if (name === "pariet_01_-_Default_0") {
                this.BrainItems = this.brainItems.find(item => item.uuid === obj.uuid);
                this.BrainIndex = this.brainItems.indexOf(this.BrainItems);

                socket.emit('SceneGameEquipmentPicking', {
                    type: 'EP',
                    body: this.BrainItems.uuid
                });
            };
        });

        /**
         * @description set ray brains used for open AI
         */
        this.ray.brains(this, (name, pos, obj) => {
            if (this.openAISys) {
                HtmlSubscription.publish('AI');
                socket.emit('SceneGameZombieChat', {
                    type: 'talk',
                    body: 'Copywriting-three'
                });
            };
        });

        CreateCombatSystem.listenToGhost.call(this, this.phyNpcs);

        CreateListenPhysicsGameWorldBiology.listenPlayer.call(this);

        CreateListenPhysicsGameWorldBiology.listenPumpkinWeapon.call(this);

        CreateListenPhysicsGameWorldBiology.listenGhost.call(this);

        const AI = new CreateAIModelSystem();
        AI.listenOpenAISystem.call(this);

        HtmlSubscription.publish("l_dom", false);

        HtmlSubscription.subscribe('killGhostEDWeight_done', async data => {
            this.BrainPhysics = await this.biology.brain(data);
            delete CreateEquipmentDropSystem.instance;
        });

        HtmlSubscription.subscribe('skill_Q_done', num => {
            delete CreateSkillQ.instance;
        });

        HtmlSubscription.subscribe('ghost_refresh_task_done', () => {
            this.io.openZombieCoordinatesScheduledTasks();
        });

        HtmlSubscription.subscribe('player_plan_save_game', () => {
            this.PlayerPhysicsData.coordinate.x = this.PlayerPhysics.position.x;
            this.PlayerPhysicsData.coordinate.z = this.PlayerPhysics.position.z;
            HtmlSubscription.publish("that_player_data", this.PlayerPhysicsData);
        });

        HtmlSubscription.subscribe('player_plan_voer_game', async () => {
            await this.io.off();
            window.location.reload();
        });

        HtmlSubscription.subscribe('game_2d_scene_get_ball', () => {
            socket.emit('SceneGame2DGetBall', 'GB');
        });

        HtmlSubscription.subscribe('game_2d_scene_count_ball', str => {
            socket.emit('SceneGame2DCountBall', {
                type: 'RP',
                body: str
            });
        });

        HtmlSubscription.subscribe('closeFight', () => {

            this.resetGhostGehavioralDecisionMaking();

            CreateBiologyActAnima.ghostStop.call(this);
            this.OpenGhostMove = false;
            this.openPlayerFindGhostMark = false;
            this.findNPCImgMark.position.set(0, 30, 0);

            GO_INTO_ACTION = false;
            HtmlSubscription.publish('fight', false);
        });

        HtmlSubscription.subscribe('eat_brain_chat', () => {
            socket.emit('SceneGameZombieChat', {
                type: 'talk',
                body: 'Copywriting-four'
            });
        });

        HtmlSubscription.subscribe('decision_making', obj => {

            if (obj.key === 'crazyBehaviorMode') return console.log('v1.1.0-beta.1 version undeveloped.');

            if (GPBD_KEY === obj.key) return console.log('Repeat key.');
            GPBD_KEY = obj.key;

            if (GPBD_POLLER) GPBD_POLLER.stop();

            const modules = obj.fd;
            const timer = modules[0].timer;
            CONSTANT_VELOCITY_GHOST = modules[0].speed ? modules[0].speed : CONSTANT_VELOCITY_GHOST;
            // console.log('CONSTANT_VELOCITY_GHOST:', CONSTANT_VELOCITY_GHOST);

            GPBD_POLLER = GhostPollingBehaviorDecision(modules, timer, module => {
                // console.log('Current module:', module);

                if (this.GhostMove) {
                    this.GhostMove = null;
                    this.progress_ghost = PROGRESS_GHOST;
                    this.velocity_ghost = CONSTANT_VELOCITY_GHOST;
                };

                this.GhostMove = new CreateNavigationPath(this, this.GroundNavMesh, this.GhostPhysicsItem.position, { x: module.position.x, y: -0.9, z: module.position.z });
                this.OpenGhostMove = true;
                this.bloodBarMove = this.bloodBar;
                this.watchOpenGhostMove.value = true;

            });
        });


        HtmlSubscription.publish('ep-count', this.PlayerPhysicsData.playerDataType.brainCount);

        HtmlSubscription.publish('kg-count', this.PlayerPhysicsData.playerDataType.ghostCount);

    };

    update(time) {

        if (this.grass) {
            this.grass.update(time);
        };

        if (this.orbitControls) {
            this.orbitControls.update();
        };

        if (this.OpenPlayerMove) {
            // console.log(this.PlayerPhysics.position);
            this.PlayerMove.init(this, this.PlayerPhysics, { progress: 'progress', velocity: 'velocity', whoMove: 'PlayerMove', whoWatchOpen: 'watchOpenPlayerMove' }, 0);
        };

        if (this.OpenPumpkinWeaponMove) {
            this.pumpkinWeaponMove.init(this, this.PhysicsPumpkin, { progress: 'progress_pumpkin', velocity: 'velocity_pumpkin', whoMove: 'pumpkinWeaponMove', whoWatchOpen: 'watchOpenPumpkinWeaponMove' }, 1);
        };

        if (this.OpenGhostMove) {
            this.bloodBarMove.position.set(this.GhostPhysicsItem.position.x, this.bloodBarMove.position.y, this.GhostPhysicsItem.position.z);
            this.GhostMove.init(this, this.GhostPhysicsItem, { progress: 'progress_ghost', velocity: 'velocity_ghost', whoMove: 'GhostMove', whoWatchOpen: 'watchOpenGhostMove' }, 2);
            this.GhostPhysicsItem.body.needUpdate = true;
        };

        if (this.OpenCreateInterstellarCoordinates) {
            this.CreateInterstellarCoordinates.anima();
        };

        if (this.OpenStartGameLookScene) {
            const { x, y, z } = this.third.camera.position;
            if (x <= 8 && y <= 10 && z <= 13) return this.OpenStartGameLookScene = false;
            this.third.camera.position.lerp(
                new THREE.Vector3(7, 1, 12),
                0.006
            );
        };

        if (this.openPlayerFindNPCQuestion && this.PlayerPhysics) {
            this.findNPCImgQuestion.position.set(this.PlayerPhysics.position.x, .4, this.PlayerPhysics.position.z);
        };

        if (this.openPlayerFindGhostMark && this.GhostMarkPhysics.exa) {
            this.findNPCImgMark.position.set(this.GhostMarkPhysics.exa.position.x, this.GhostMarkPhysics.exa.position.y + .5, this.GhostMarkPhysics.exa.position.z);
        };

        if (this.openPlayerAR && this.playerCircle && this.PlayerPhysics) {
            this.playerCircle.position.set(this.PlayerPhysics.position.x, this.PlayerPhysics.position.y + .4, this.PlayerPhysics.position.z);
            this.playerCircle.body.needUpdate = true;
        };

        if (this.openPlayerARForWA && this.playerCircleWA && this.PlayerPhysics) {
            this.playerCircleWA.position.set(this.PlayerPhysics.position.x, this.PlayerPhysics.position.y + .4, this.PlayerPhysics.position.z);
            this.playerCircleWA.body.needUpdate = true;
        };

        if (this.openPumpkinWeaponAR && this.pumpkinWeaponCircle && this.PhysicsPumpkin) {
            this.pumpkinWeaponCircle.position.set(this.PhysicsPumpkin.position.x, this.PhysicsPumpkin.position.y, this.PhysicsPumpkin.position.z);
            this.pumpkinWeaponCircle.body.needUpdate = true;
        };

        if (this.PhysicsBrains && this.PlayerPhysics) {
            this.PhysicsBrains.lookAt(this.PlayerPhysics.position);
        };

    };

    /** HEADE */
    getPointer() {
        /** calculate mouse position in normalized device coordinates */
        // (-1 to +1) for both components
        const pointer = this.input.activePointer;
        const x = (pointer.x / this.cameras.main.width) * 2 - 1;
        const y = -(pointer.y / this.cameras.main.height) * 2 + 1;
        return { x, y };
    };
    /** TAIL */

    cameraLookSandTable() {
        this.third.camera.position.set(10, 42, 56);
        this.third.camera.lookAt(0, 0, 0);
    };

    watch(initialValue, callback) {
        let value = initialValue;
        return {
            get value() {
                return value;
            },
            set value(newValue) {
                const oldValue = value;
                value = newValue;
                callback(oldValue, newValue);
            }
        };
    };

    cleanupResources() {

        if (this.GhostPhysics && this.GhostPhysics.length > 0) {
            this.GhostPhysics.forEach(item => {
                this.cleanupObject3D(item.example);
            });
            this.GhostPhysics = [];
        };

        this.NPCBloodBars.forEach(bar => {
            this.third.scene.remove(bar);
            bar.geometry.dispose();
            bar.material.dispose();
        });
        this.NPCBloodBars = [];
    }

    cleanupObject3D(object) {
        if (!object) return;

        if (object.parent) {
            object.parent.remove(object);
        };

        if (object.body) {
            this.third.physics.destroy(object.body);
        };

        if (object.anims && object.anims.mixer) {
            object.anims.mixer.stopAllAction();
            object.anims.mixer.uncacheRoot(object);
            if (object.anims.mixer._actions) {
                object.anims.mixer._actions.forEach(action => {
                    object.anims.mixer.uncacheAction(action);
                });
            };
        };

        object.traverse(child => {
            if (child.isMesh) {
                if (child.geometry) child.geometry.dispose();
                if (child.material) {
                    if (Array.isArray(child.material)) {
                        child.material.forEach(m => m.dispose());
                    } else {
                        child.material.dispose();
                    };
                };
            };
        });
    };

    cleanupResourcesItem(uuid) {

        if (this.GhostPhysics && this.GhostPhysics.length > 0) {
            this.GhostPhysics.forEach((item, index) => {
                if (item.example.uuid === uuid) {
                    this.cleanupObject3DItem(item.example);
                    this.GhostPhysics.splice(index, 1);
                    this.phyNpcs.splice(index, 1);
                };
            });
        };

        this.NPCBloodBars.forEach((bar, index) => {
            if (bar.uuid === uuid) {
                this.third.scene.remove(bar);
                bar.geometry.dispose();
                bar.material.dispose();
                this.NPCBloodBars.splice(index, 1);
            };
        });
    };

    cleanupObject3DItem(object) {
        if (!object) return;

        if (object.parent) {
            object.parent.remove(object);
        };

        if (object.body) {
            this.third.physics.destroy(object.body);
        };

        if (object.anims && object.anims.mixer) {
            object.anims.mixer.stopAllAction();
            object.anims.mixer.uncacheRoot(object);
            if (object.anims.mixer._actions) {
                object.anims.mixer._actions.forEach(action => {
                    object.anims.mixer.uncacheAction(action);
                });
            };
        };

        object.traverse(child => {
            if (child.isMesh) {
                if (child.geometry) child.geometry.dispose();
                if (child.material) {
                    if (Array.isArray(child.material)) {
                        child.material.forEach(m => m.dispose());
                    } else {
                        child.material.dispose();
                    };
                };
            };
        });
    };

    async killGhostItem(uuid) {

        this.OpenGhostMove = false;

        this.resetGhostGehavioralDecisionMaking();

        this.openPlayerFindGhostMark = false;
        this.findNPCImgMark.position.set(0, 30, 0);

        const item = this.GhostPhysics.find(item => item.data.uuid === uuid);
        await CreateBiologyActAnima.ghostDie(item);
        await CreateBiologyActAnima.ghostDisappear(item);

        this.cleanupResourcesItem(uuid);

        if (this.ray) {
            this.ray.updateNpcObjectsItem(uuid);
        };

        CreateCombatSystem.myLinkedList.removeItem(uuid);

        CreateEquipmentDropSystem.main(uuid);

        socket.emit('SceneGameKillGhostCount', {
            type: 'KG',
            body: uuid
        });

        if (this.GhostPhysics.length === 0 && this.phyNpcs.length === 0) {
            console.log('Ghost clearing the scene!');

            HtmlSubscription.publish("ghost_refresh_task", true);

        };

        GO_INTO_ACTION = false;
        HtmlSubscription.publish('fight', false);
    };

    async resetSceneGhostSystem() {

        this.OpenGhostMove = false;

        this.resetGhostGehavioralDecisionMaking();

        this.cleanupResources();

        this.openPlayerFindGhostMark = false;
        this.findNPCImgMark.position.set(0, 30, 0);

        this.GhostPhysics = await this.biology.npc();

        this.meshNpcs = [];
        this.phyNpcs = [];
        this.GhostPhysics.forEach(it => {
            this.phyNpcs.push({
                uuid: it.data.uuid,
                exa: it.example,
                play: it.play
            });
            it.mesh.map(ite => {
                this.meshNpcs.push(ite.mesh);
            });
        });
        if (this.ray) {
            this.ray.updateNpcObjects(this.meshNpcs);
        };

        CreateCombatSystem.listenToGhost.call(this, this.phyNpcs);

        HtmlSubscription.publish("ghost_refresh_task", false);

        GO_INTO_ACTION = false;
        HtmlSubscription.publish('fight', false);

    };

    async resetGhostGehavioralDecisionMaking() {
        if (GPBD_POLLER) GPBD_POLLER.stop();
        CONSTANT_VELOCITY_GHOST = 1;
        GPBD_KEY = '';
    };

};
